﻿Imports AvianWaves.AvianPlay.Language
Imports AvianWaves.AvianPlay.ErrorLog

Namespace Library
  Public Class MenuBuilder
    Public Event CallAsyncPluginAssignItemsOnMainThread()
    Public Event SetListEnabled(ByVal enabled As Boolean)

    Private _libIni As New Ini.File()
    Private _currentMenu As New Stack(Of String)
    Private _variables As New Stack(Of List(Of Collections.Generic.KeyValuePair(Of String, String)))
    Private _items As New MobilePrettyGui.EnumerableInterfaceList(Of LibraryEntry, MobilePrettyGui.iPrettyListItem)

    Private _lastRefreshTime As Date = Date.MinValue
    Private _lastQueryTable As String = ""
    Private _lastQueryWhere As AvianPlayDB.WhereClause = Nothing
    Private _lastQueryOrder As String = ""
    Private _lastFilePath As String = ""
    Private _lastIsQuery As Boolean = False
    Private _curPlugin As Plugin.iLibraryPlugin = Nothing
    Private _waitingForAsyncPluginMenu As Boolean = False

    Private _db As AvianPlayDB


    Public Sub New(ByVal db As AvianPlayDB)
      Me._db = db
      Me._libIni.LoadFile(Settings.INILibraryPath)
      Me.ResetMenu()
    End Sub

    Public ReadOnly Property LastRefreshTime() As Date
      Get
        Return Me._lastRefreshTime
      End Get
    End Property

    Public ReadOnly Property ItemsAreAVPLEntries() As Boolean
      Get
        If Me._items.Count > 0 Then
          If Me._items(0).AVPLRef IsNot Nothing Then
            Return True
          End If
        End If
        Return False
      End Get
    End Property

    Public ReadOnly Property LastQueryTable() As String
      Get
        Return Me._lastQueryTable
      End Get
    End Property

    Public ReadOnly Property LastQueryWhere() As AvianPlayDB.WhereClause
      Get
        Return Me._lastQueryWhere
      End Get
    End Property

    Public ReadOnly Property LastQueryOrder() As String
      Get
        Return Me._lastQueryOrder
      End Get
    End Property

    Public ReadOnly Property LastFilePath() As String
      Get
        Return Me._lastFilePath
      End Get
    End Property

    Public ReadOnly Property LastIsQuery() As Boolean
      Get
        Return Me._lastIsQuery
      End Get
    End Property

    Public ReadOnly Property CurrentMenu() As String
      Get
        If Me._currentMenu.Count > 0 Then
          Return Me._currentMenu.Peek.ToString
        Else
          Return ""
        End If
      End Get
    End Property

    Public ReadOnly Property Items() As MobilePrettyGui.EnumerableInterfaceList(Of LibraryEntry, MobilePrettyGui.iPrettyListItem)
      Get
        Return Me._items
      End Get
    End Property

    Public Sub ClearItems()
      For Each entry As LibraryEntry In Me._items
        entry.Dispose()
      Next
      Me._items.Clear()
    End Sub

    Public Sub ResetMenu()
      Me._currentMenu.Clear()
      Me._variables.Clear()
      Me.NextMenu("Main")
    End Sub

    ''' <summary>
    ''' Navigate to the previous menu (or cancel an async operation)
    ''' </summary>
    ''' <returns>TRUE if going to the previous menu, FALSE if cancelling an asyncronous operation</returns>
    Public Function PreviousMenu() As Boolean
      PreviousMenu = True
      If Me._curPlugin IsNot Nothing Then
        If Me._waitingForAsyncPluginMenu Then
          Me._curPlugin.CancelAsync()
          RaiseEvent SetListEnabled(True)
          PreviousMenu = False ' Because we are cancelling an async operation
        ElseIf Me._curPlugin.PreviousMenu Then
          ' Load the items from the _curPlugin (it's okay to do it here because PreviousMenu is not an asynchronous call)
          Me.AssignPluginItems()
        Else
          ' There are no more menus in the plugin's menu stack, so we are back into the internal menu structure, 
          Try : RemoveHandler Me._curPlugin.NextMenuAsyncCompleted, AddressOf CurPlugin_NextMenuAsyncCompleted : Catch : End Try
          Try : RemoveHandler Me._curPlugin.GetApplicationSetting, AddressOf CurPlugin_GetApplicationSetting : Catch : End Try
          Me._curPlugin = Nothing
          Me._currentMenu.Pop()
          Me._variables.Pop()
          Me.NextMenu(Me._currentMenu.Pop, Me._variables.Pop)
        End If
      ElseIf Me._currentMenu.Count >= 2 AndAlso Me._currentMenu.Count = Me._variables.Count Then
        Me._currentMenu.Pop()
        Me._variables.Pop()
        Me.NextMenu(Me._currentMenu.Pop, Me._variables.Pop)
      Else
        Me.ResetMenu()
        Me.NextMenu("Main")
      End If
    End Function

    Public Sub RefreshMenu()
      If Me._currentMenu.Count >= 1 AndAlso Me._currentMenu.Count = Me._variables.Count Then
        Me.NextMenu(Me._currentMenu.Pop, Me._variables.Pop)
      Else
        Me.ResetMenu()
        Me.NextMenu("Main")
      End If
    End Sub

    Public ReadOnly Property CanGoPreviousMenu() As Boolean
      Get
        If Me._curPlugin IsNot Nothing _
         OrElse Me._currentMenu.Peek.ToUpper <> "MAIN" _
        Then
          Return True
        Else
          Return False
        End If
      End Get
    End Property

    Public Function NextMenu(ByVal submenu As String) As Boolean
      Return NextMenu(submenu, DirectCast(Nothing, List(Of KeyValuePair(Of String, String))))
    End Function

    Public Function NextMenu(ByVal submenu As String, ByVal le As LibraryEntry) As Boolean
      If le IsNot Nothing Then
        Return NextMenu(submenu, le.Variables)
      Else
        Return NextMenu(submenu)
      End If
    End Function

    Public Function NextMenu(ByVal submenu As String, ByVal var As List(Of KeyValuePair(Of String, String))) As Boolean
      NextMenu = True

      If Me._curPlugin IsNot Nothing Then
        If Me._curPlugin.NextMenu(submenu) Then
          NextMenu = Me.AssignPluginItems()
        Else
          Me._waitingForAsyncPluginMenu = True
          RaiseEvent SetListEnabled(False)
          NextMenu = False
        End If
      Else
        Dim section As Ini.Section = Me._libIni.Section(submenu)

        If section IsNot Nothing Then
          Dim mode As String = section.StringValue("Mode")

          If mode IsNot Nothing Then
            Dim variables As List(Of KeyValuePair(Of String, String))

            ' Determine what variables we are passing on
            If var IsNot Nothing Then
              variables = New List(Of KeyValuePair(Of String, String))(var)
            ElseIf Me._variables.Count > 0 Then
              variables = New List(Of KeyValuePair(Of String, String))(Me._variables.Peek())
            Else
              variables = New List(Of KeyValuePair(Of String, String))
            End If

            ' Add the variables and submenu to the history
            Me._currentMenu.Push(submenu)
            Me._variables.Push(variables)

            ' Clear out the menu now
            Me.ClearItems()

            ' Take action, based on menu type
            Select Case mode.Trim.ToUpper
              Case "MENUS"
                Me.LoadMenus(section)
              Case "SUBMENUQUERY"
                Me.LoadSubMenuQuery(section)
              Case "ITEMQUERY"
                Me.LoadItemQuery(section)
              Case "ITEMPLAYLIST"
                Me.LoadItemPlaylist(section)
              Case "PLUGIN"
                Dim pluginKey As String = section.StringValue("Plugin")
                If pluginKey IsNot Nothing Then
                  NextMenu = Me.LoadPlugin(pluginKey)
                End If
            End Select
          End If
        End If
      End If

      Me._lastRefreshTime = Date.Now
    End Function

    Private Sub LoadMenus(ByVal section As Ini.Section)
      For Each key As String In section.Keys
        If Left(key.Trim, 7).ToUpper = "SUBMENU" Then
          Dim entry As New LibraryEntry
          entry.SubmenuKey = section.StringValueNoNull(key)
          entry.Text = GetLanguageString(entry.SubmenuKey)
          entry.Variables = Nothing
          Me._items.Add(entry)
        End If
      Next
    End Sub

    Private Sub LoadSubMenuQuery(ByVal section As Ini.Section)
      Dim kSelect As String = section.StringValue("SubMenuQuerySelect")
      If kSelect IsNot Nothing Then ' If no select portion, the query is impossible, so the menu is empty
        Dim sSelect As String = kSelect
        Dim lWhere As New List(Of String)
        Dim lOrder As New List(Of String)
        Dim aaWhere As New List(Of String)
        Dim aaQuery As String = ""
        Dim submenu As String = ""
        Dim submenuEmptyString As String = ""
        Dim textColumn As String = ""
        Dim skiple As LibraryEntry = Nothing

        ' Fetch the other query parameters
        For Each k As String In section.Keys
          Select Case Left(k.Trim, 17).ToUpper
            Case "SUBMENUSKIPITEM"
              Dim val() As String = section.StringValueNoNull(k).Split("|")
              If val.Length = 3 Then
                skiple = New LibraryEntry
                skiple.SubmenuKey = val(0).Trim
                skiple.Text = Language.GetLanguageString(val(1).Trim).Replace("%", Me.ParseVariables(val(2), True).Trim)
                skiple.Variables = New List(Of Collections.Generic.KeyValuePair(Of String, String))(Me._variables.Peek)
              Else
                ErrorLog.WriteToErrorLog("LoadSubMenuQuery :: SubMenuSkipItem has invalid number of parts (should be 3): " & val.Length)
              End If
            Case "SUBMENUQUERYWHERE"
              lWhere.Add(section.StringValueNoNull(k))
            Case "SUBMENUQUERYORDER"
              lOrder.Add(section.StringValueNoNull(k))
            Case "SUBMENUQUERYGO"
              submenu = section.StringValueNoNull(k)
            Case "SUBMENUTEXTCOLUMN"
              textColumn = section.StringValueNoNull(k).ToUpper
            Case "SUBMENUTEXTEMPTY"
              submenuEmptyString = GetLanguageString(section.StringValueNoNull(k))
            Case "SUBMENUTHUMBWHERE"
              aaWhere.Add(section.StringValueNoNull(k))
          End Select
        Next

        ' Replace in-string variables
        sSelect = Me.ParseVariables(sSelect)
        Me.ParseVariablesList(lWhere)
        Me.ParseVariablesList(lOrder)

        If sSelect.Trim.Length > 0 AndAlso Not sSelect.Contains("{") AndAlso Not sSelect.Contains("}") Then
          ' Execute query
          Using cmd = Me._db.CreateAdHocCommand
            ' Create query
            cmd.CommandText = "SELECT " & sSelect

            ' Add WHERE clauses
            cmd.CommandText &= " WHERE ([NotFound] = 0) "

            ' Primary query WHERE
            Dim wc As New AvianPlayDB.WhereClause
            For i As Integer = 0 To lWhere.Count - 1
              Dim ws = StringSplitByString(lWhere(i), "--")
              If ws.Count = 2 Then
                wc.AddItem(New AvianPlayDB.WhereClause.WhereItem(ws(0).Trim, ws(1).Trim))
              End If
            Next

            ' Add the query WHERE to the query
            For i As Integer = 0 To wc.Count - 1
              cmd.CommandText &= " AND " & wc.SQLCEWhereClause(i)
              cmd.Parameters.Add(wc.SQLCEParameter(i))
            Next

            ' Add ORDER BY clauses
            For i As Integer = 0 To lOrder.Count - 1
              If i = 0 Then
                cmd.CommandText &= " ORDER BY " & lOrder(i)
              Else
                cmd.CommandText &= ", " & lOrder(i)
              End If
            Next

            ' Add the default item, if it exists
            If skiple IsNot Nothing Then
              Me._items.Add(skiple)
            End If

            ' Add the rest of the list
            Using result = cmd.ExecuteReader
              While result.Read
                Dim entry As New LibraryEntry

                entry.SubmenuKey = submenu

                If Me._variables.Count > 0 Then
                  entry.Variables = New List(Of Collections.Generic.KeyValuePair(Of String, String))(Me._variables.Peek)
                Else
                  entry.Variables = New List(Of Collections.Generic.KeyValuePair(Of String, String))
                End If

                For i As Integer = 0 To result.FieldCount - 1
                  Dim colName As String = result.GetName(i).Trim
                  Dim value As String = result.GetValue(i).ToString.Trim
                  entry.Variables.Add(New KeyValuePair(Of String, String)(colName, value))
                  If colName.ToUpper = textColumn Then
                    If value.Length > 0 Then
                      entry.Text = value
                    Else
                      entry.Text = submenuEmptyString
                    End If
                  End If
                Next

                ' Album Art WHERE
                Dim aa As New AvianPlayDB.WhereClause
                For i As Integer = 0 To aaWhere.Count - 1
                  Dim ws = StringSplitByString(aaWhere(i), "--")
                  If ws.Count = 2 Then
                    aa.AddItem(New AvianPlayDB.WhereClause.WhereItem(ws(0).Trim, ws(1).Trim))
                  End If
                Next

                ' set the thumbnail query where clause
                entry.SetThumbnailQueryWhere(aa)

                Me._items.Add(entry)
              End While
            End Using
          End Using
        End If
      End If
    End Sub

    Private Sub LoadItemQuery(ByVal section As Ini.Section)
      Dim lWhere As New List(Of String)
      Dim lOrder As New List(Of String)
      Dim tableName As String = String.Empty
      Dim limit As Integer = 0

      ' Fetch the other query parameters
      For Each k As String In section.Keys
        Select Case Left(k.Trim.ToUpper, 14)
          Case "ITEMQUERYTABLE"
            tableName = section.StringValueNoNull(k)
          Case "ITEMQUERYWHERE"
            lWhere.Add(section.StringValueNoNull(k))
          Case "ITEMQUERYORDER"
            lOrder.Add(section.StringValueNoNull(k))
          Case "ITEMQUERYLIMIT"
            limit = section.IntegerValueNoNull(k)
        End Select
      Next

      ' Clean up the table name
      tableName = tableName.Replace("[", "").Replace("]", "").Trim.ToUpper

      ' Replace in-string variables
      Me.ParseVariablesList(lWhere)
      Me.ParseVariablesList(lOrder)

      ' Build WHERE clause
      Dim wc As New AvianPlayDB.WhereClause
      For Each w As String In lWhere
        Dim ws = StringSplitByString(w, "--")
        If ws.Count = 2 Then
          wc.AddItem(New AvianPlayDB.WhereClause.WhereItem(ws(0).Trim, ws(1).Trim))
        End If
      Next

      ' Build ORDER BY clauses
      Dim oquery As String = ""
      For i As Integer = 0 To lOrder.Count - 1
        If i > 0 Then oquery &= ", "
        oquery &= lOrder(i)
      Next

      ' Store the query parameters for later refresh (if needed)
      Me._lastQueryTable = tableName
      Me._lastQueryWhere = wc
      Me._lastQueryOrder = oquery
      Me._lastIsQuery = True

      ' Execute query
      If tableName = "FILELIBRARY" Then ' Eventually we might do queries right on the actual table in the ini file, but for now we look for the two known tables that are supported
        Dim fts As List(Of AvianPlayDB.AudioFileTable) = Me._db.GetAudioFileDBData(wc, oquery, False, False, limit)
        If fts IsNot Nothing Then
          For Each ft As AvianPlayDB.AudioFileTable In fts
            Dim entry As New LibraryEntry
            Dim ape As New Playlists.AVPLPlaylistEntry
            AvianPlayDB.FillPlayListEntryFromFileTable(ft, ape)
            entry.Variables = Nothing
            entry.AVPLRef = ape
            Me._items.Add(entry)
          Next
        End If
      ElseIf tableName = "PLAYLISTLIBRARY" Then
        Dim fts As List(Of AvianPlayDB.PlaylistFileTable) = Me._db.GetPlaylistFileDBData(wc, oquery, False, limit)
        If fts IsNot Nothing Then
          For Each ft As AvianPlayDB.PlaylistFileTable In fts
            Dim entry As New LibraryEntry
            entry.Text = ft.Title
            entry.Subtext = ft.FilePath
            entry.PlaylistPath = ft.FilePath
            Me._items.Add(entry)
          Next
        End If
      ElseIf tableName.Length <= 0 Then
        WriteToErrorLog("LoadItemQuery :: ItemQueryTable not specified")
      Else
        WriteToErrorLog("LoadItemQuery :: Unknown table name: " & tableName)
      End If
    End Sub

    Private Sub LoadItemPlaylist(ByVal section As Ini.Section)
      Dim file As String = section.StringValue("ItemPlaylist")
      If file IsNot Nothing Then
        Dim ap As New Playlists.AVPLPlaylist

        Try
          ap.LoadFromFile(file)
          ' Store the file parameters for later refresh (if needed)
          Me._lastFilePath = file
          Me._lastIsQuery = False

          For Each ape As Playlists.AVPLPlaylistEntry In ap
            ' Refresh all fields in playlist entry
            AvianPlayDB.FillPlayListEntryFromDB(ape.FilePath, Me._db, ape)

            Dim entry As New LibraryEntry
            entry.AVPLRef = ape
            entry.Variables = Nothing
            Me._items.Add(entry)
          Next
        Catch ex As Exception
          WriteToErrorLog("Failed to load playlist """ & file & """ :: " & ex.ToString)
        End Try
      End If
    End Sub


    Private Sub ParseVariablesList(ByRef strs As List(Of String))
      Dim removeList As New List(Of String)

      For i As Integer = 0 To strs.Count - 1
        strs(i) = Me.ParseVariables(strs(i))
        If strs(i).Contains("{") OrElse strs(i).Contains("}") Then
          removeList.Add(strs(i))
        End If
      Next

      ' Get rid of any lines with unresolved variables
      For Each remove As String In removeList
        strs.Remove(remove)
      Next
    End Sub

    Private Function ParseVariables(ByVal str As String, Optional ByVal removeNotFoundVariables As Boolean = False) As String
      Dim strret As String = str
      If Me._variables.Count > 0 Then
        For Each var As KeyValuePair(Of String, String) In Me._variables.Peek
          strret = StringCaseInsensitiveReplace(strret, "{" & var.Key & "}", var.Value)
        Next
      End If
      If removeNotFoundVariables Then
        strret = StringRemoveBetween(strret, "{", "}")
      End If
      Return strret
    End Function

    Private Function LoadPlugin(ByVal moduleName As String) As Boolean
      LoadPlugin = True

      Dim ass As System.Reflection.Assembly
      Dim found As Boolean = False

      Try
        ass = System.Reflection.Assembly.LoadFrom(Settings.ApplicationPath & "\Library\" & moduleName)
        If ass IsNot Nothing Then
          For Each t As Type In ass.GetTypes
            For Each i As Type In t.GetInterfaces
              If i Is GetType(Library.Plugin.iLibraryPlugin) Then
                ' Create an instance of the plugin
                Me._curPlugin = Activator.CreateInstance(t)

                ' Add handlers for the events
                AddHandler Me._curPlugin.NextMenuAsyncCompleted, AddressOf CurPlugin_NextMenuAsyncCompleted
                AddHandler Me._curPlugin.GetApplicationSetting, AddressOf CurPlugin_GetApplicationSetting

                ' Set the language
                Me._curPlugin.SetLanguageStrings(Language.GetLanguageReader())

                ' Set the colors
                Dim col As New Plugin.Colors
                col.BackgroundColor = Skin.FormBackgroundColor
                col.BackgroundTextboxColor = Skin.FormBackgroundColorTextBox
                col.ForegroundColor = Skin.FormForegroundColor
                col.ForegroundInactiveColor = Skin.FormForegroundInactiveColor
                Me._curPlugin.SetColors(col)

                ' Now execute the first NextMenu call
                Me.NextMenu("")

                ' Assuming no exception, all is well!
                found = True
                Exit For
              End If
            Next
            If found Then
              Exit For
            End If
          Next
        End If
      Catch ex As Exception
        Dim loadErr As String = GetLanguageString("UnableToLoadPlug").Replace("%", moduleName)
        MessageBox.Show(loadErr & vbCrLf & vbCrLf & ex.Message)
        WriteToErrorLog("Unable to load plugin " & moduleName & " :: " & ex.ToString)
        Try : RemoveHandler Me._curPlugin.NextMenuAsyncCompleted, AddressOf CurPlugin_NextMenuAsyncCompleted : Catch : End Try
        Try : RemoveHandler Me._curPlugin.GetApplicationSetting, AddressOf CurPlugin_GetApplicationSetting : Catch : End Try
        Me._curPlugin = Nothing
        LoadPlugin = False
      End Try
    End Function

    Private Sub CurPlugin_NextMenuAsyncCompleted()
      Try
        If Me._waitingForAsyncPluginMenu Then
          RaiseEvent CallAsyncPluginAssignItemsOnMainThread()
          Me._waitingForAsyncPluginMenu = False
        End If
      Catch ex As Exception
        WriteToErrorLog("LIBRARY PLUGIN :: CurPlugin_NextMenuAsyncCompleted :: " & ex.ToString)
        MessageBox.Show(GetLanguageString("LibraryPluginItemAssignmentError"), "Avian Play", MessageBoxButtons.OK, MessageBoxIcon.Hand, MessageBoxDefaultButton.Button1)
      End Try
    End Sub

    Private Sub CurPlugin_GetApplicationSetting(ByVal section As String, ByVal key As String, ByRef value As String, ByRef keyFound As Boolean)
      Dim val As String = Settings.GetSetting(section, key)
      If val IsNot Nothing Then
        value = val
        keyFound = True
      Else
        value = ""
        keyFound = False
      End If
    End Sub

    Public Function AssignPluginItems() As Boolean
      Try
        AssignPluginItems = True

        If Me._curPlugin IsNot Nothing Then
          If Me._curPlugin.Items IsNot Nothing Then
            Me.ClearItems()
            For Each ili As Library.Plugin.iLibraryItem In Me._curPlugin.Items
              Dim item As New LibraryEntry
              If ili.AVPLRef Is Nothing Then
                item.Text = ili.Text
                item.Subtext = ili.Subtext
                item.Thumbnail = ili.Icon
                item.SubmenuKey = ili.Key
              Else
                item.AVPLRef = ili.AVPLRef
              End If
              Me._items.Add(item)
            Next
          Else ' No menu progression has happened, tell the calling method/thread this, so that menu position stack is taken care and doesn't have an extra entry
            AssignPluginItems = False
          End If

          RaiseEvent SetListEnabled(True)
        End If
      Catch ex As Exception
        WriteToErrorLog("LIBRARY PLUGIN :: AssignPluginItems :: " & ex.ToString)
        MessageBox.Show(GetLanguageString("LibraryPluginItemAssignmentError"), "Avian Play", MessageBoxButtons.OK, MessageBoxIcon.Hand, MessageBoxDefaultButton.Button1)
      End Try
    End Function

    Public ReadOnly Property IsLibraryVersionValid() As Boolean
      Get
        If Me.LibraryVersion = 1 Then
          Return True
        End If
        Return False
      End Get
    End Property

    Public ReadOnly Property SuggestLibraryUpgrade() As Boolean
      Get
        If Me.LibraryVersion <> 1 Then
          Return True
        End If
      Return False
      End Get
    End Property

    Public ReadOnly Property LibraryVersion() As Integer
      Get
        If Me._libIni IsNot Nothing Then
          Return Me._libIni.SectionNoNull("Meta").IntegerValueNoNull("Version", -1)
        End If
      End Get
    End Property

    Public Sub ResetLibraryVersion()
      If Me._libIni IsNot Nothing Then
        Dim sec As Ini.Section = Me._libIni.Section("Meta")
        If sec Is Nothing Then
          Me._libIni.AddSection("Meta")
          sec = Me._libIni.Section("Meta")
        End If
        sec.IntegerValue("Version") = 1
        Me._libIni.Save(Settings.INILibraryPath, True)
      End If
    End Sub

    Public Sub ResetLibraryINIFile()
      IO.File.Copy(Settings.INILibraryOriginalPath, Settings.INILibraryPath, True)
    End Sub

  End Class
End Namespace